﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlServerCe;
using System.Data;
using Shared;

namespace Kea
{
    public class TRWManager<T> : IDisposable where T : TableReaderWriter<T>, new()
    {
        internal SqlCeResultSet ResultSet { get; private set; }
        internal SqlCeUpdatableRecord UpdatableRecord { get; private set; }
        internal IDataRecord ReadData { get { return (IDataRecord)UpdatableRecord ?? ResultSet; } }


        public T Item { get; private set; }
        private string _indexName;
        private string _tableName;
        private readonly SqlCeConnection _cnn;
        private readonly LinkedFilter<T> _filters;
        internal TRWManager(DataPlanBaseTRW<T> dtb) : this(dtb, EditStatus.ReadOnly) { }
        internal TRWManager(DataPlanBaseTRW<T> dtb, EditStatus editStatus)
        {
            _filters = dtb.Filters;
            Item = new T();

            using (SqlCeCommand cmd = new SqlCeCommand())
            {
                cmd.Connection = _cnn = Item.ConnectionGet();
                cmd.CommandType = CommandType.TableDirect;
                cmd.CommandText = _tableName = Item.GetTableName();

                if (editStatus != EditStatus.Createable)
                {
                    cmd.IndexName = _indexName = dtb.IndexName ?? DatabaseInfo.GetPrimaryKeyName(_cnn, _tableName);
                    //TODO check that when a null is used to match on an index it works.
                    if (dtb.IndexValuesLowerLimit != null && dtb.LowerAndUpperIndexValuesMatch)
                        cmd.SetRange(DbRangeOptions.Prefix, dtb.IndexValuesLowerLimit, null);
                    else if (dtb.IndexValuesLowerLimit != null || dtb.IndexValuesUpperLimit != null)
                        cmd.SetRange(DbRangeOptions.Default, dtb.IndexValuesLowerLimit, dtb.IndexValuesUpperLimit);
                }
                ResultSet = cmd.ExecuteResultSet(editStatus == EditStatus.ReadOnly ? ResultSetOptions.None : ResultSetOptions.Updatable);
            }
            Item.TRWManager = this;
        }

        internal void Delete() { ResultSet.Delete(); }

        internal void CreateNewInternal()
        {
            UpdatableRecord = ResultSet.CreateRecord();
        }
        internal void SaveNewInternal()
        {
            if (UpdatableRecord == null) throw new Exception("Must call CreateNew before SaveNew.");
            Item.CommitMutableChanges();
            ResultSet.Insert(UpdatableRecord, DbInsertOptions.PositionOnInsertedRow);
            UpdatableRecord = null;
        }

        internal bool SaveUpdateInternal()
        {
            Item.CommitMutableChanges();
            ResultSet.Update();
            return true;//todo check record has changed
        }
        internal bool ReadInternalForModification()
        {
            Item.ChangeRowVersion();
            return ReadInternal();
        }
        internal bool ReadInternal()
        {
            while (ResultSet.Read()) if (_filters == null || _filters.Where(Item)) return true;
            return false;
        }

        public static TMan GetFirst<TMan>(TMan man)
            where TMan : TRWManager<T>
        {
            try
            {
                if (man.ReadInternalForModification()) return man;
                throw new InvalidOperationException("TODO could not read one row.");
            }
            catch
            {
                ((IDisposable)man).Dispose();
                throw;
            }
        }


        void IDisposable.Dispose()
        {
            ResultSet.Dispose();
            Item.ConnectionRelease(_cnn);
        }




        private KeyMatcher _km;

        internal void LoadIndexDetails()
        {
            _km = KeyMatcher.CreateKeyMatcher(_cnn, _tableName, _indexName, ResultSet);
        }

        internal bool ReadToStartOfRange(object[] keyValues)
        {
            try
            {
                if (!ResultSet.Seek(DbSeekOptions.FirstEqual, keyValues) || !ResultSet.Read()) return false;
            }
            catch (Exception ex)
            {
                throw CreateDetailedSeekException(ex, keyValues);
            }
            if (_filters == null || _filters.Where(Item)) return true;
            while (ResultSet.Read())
            {
                if (!_km.KeyMatches(keyValues)) return false;
                if (_filters == null || _filters.Where(Item)) return true;
            }
            return false;
        }

        internal bool ReadNextInRange(object[] keyValues)
        {
            while (ResultSet.Read() && _km.KeyMatches(keyValues)) if (_filters == null || _filters.Where(Item)) return true;
            return false;
        }

        private Exception CreateDetailedSeekException(Exception ex, object[] keyValues)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(ex.GetType().Name);
                sb.Append(" occured trying to Seek against index '");
                sb.Append(_indexName);
                sb.Append("' on table '");
                sb.Append(_tableName);
                sb.Append("'. Values used - ");

                for (int i = 0; i < keyValues.Length; i++)
                {
                    if (i > 0) sb.Append(", ");
                    object v = keyValues[i];
                    if (v == null)
                    {
                        sb.Append("[NULL REFERENCE]");
                    }
                    else
                    {
                        sb.Append("'");
                        sb.Append(v);
                        sb.Append("' (");
                        sb.Append(v.GetType().Name);
                        sb.Append(")");
                    }
                }

                sb.Append(".");

                return new Exception(sb.ToString(), ex);
            }
            catch
            {
                return ex;
            }
        }


        //private object[] _keys;
        //internal bool Seek(object key)
        //{
        //    object[] keys = key as object[];
        //    if (keys == null) (keys = _keys ?? (_keys = new object[1]))[0] = key;

        //    try
        //    {
        //        return ResultSet.Seek(DbSeekOptions.AfterEqual, keys) && ResultSet.Read();
        //    }
        //    catch (Exception ex)
        //    {
        //        throw CreateDetailedSeekException(ex, keys);
        //    }

        //}

    }

    public class TRWManagerWithCreate<T> : TRWManager<T> where T : TableReaderWriter<T>, new()
    {
        internal TRWManagerWithCreate() : base(new DataPlanStart<T>(), EditStatus.Createable) { }
        public void Save() { base.SaveNewInternal(); }
        public void CreateNew() { base.CreateNewInternal(); }
    }

    public class TRWManagerWithSave<T> : TRWManager<T> where T : TableReaderWriter<T>, new()
    {
        internal TRWManagerWithSave(DataPlanBaseTRW<T> dtb) : base(dtb, EditStatus.Updateable) { }
        public bool Save() { return base.SaveUpdateInternal(); }
    }
    public class TRWManagerWithSaveAndNavigate<T> : TRWManagerWithSave<T> where T : TableReaderWriter<T>, new()
    {
        internal TRWManagerWithSaveAndNavigate(DataPlanBaseTRW<T> dtb) : base(dtb) { }
        public bool Read() { return base.ReadInternalForModification(); }
    }
    public class TRWManagerWithNavigate<T> : TRWManager<T> where T : TableReaderWriter<T>, new()
    {
        internal TRWManagerWithNavigate(DataPlanBaseTRW<T> dtb) : base(dtb) { }
        public bool Read() { return base.ReadInternalForModification(); }
    }

    //    public class TRWManagerWithSaveAndNavigateWithItem0<T, T0> : TRWManagerWithSaveAndNavigate<T> where T : TableReaderWriter<T>, new()
    //  {
    //    internal TRWManagerWithSaveAndNavigateWithItem0(DataThingBase<T> dtb) : base(dtb) { }
    //  public T0 Item0 { get; internal set; }
    //}
    internal enum EditStatus : byte { ReadOnly, Updateable, Createable }
}
